package link.luyu.demo.port;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import link.luyu.protocol.network.CallRequest;
import link.luyu.protocol.network.CallResponse;
import link.luyu.protocol.network.Receipt;
import link.luyu.protocol.network.Transaction;
import link.luyu.sdk.caller.LuyuSDK;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Contract {
    private static final Logger logger = LoggerFactory.getLogger(Application.class);

    private LuyuSDK sdk;
    private String supplyContractPath;
    private String portContractPath;
    private ObjectMapper om;

    public Contract(LuyuSDK sdk, String scp, String pcp) {
        this.sdk = sdk;
        this.supplyContractPath = scp;
        this.portContractPath = pcp;
        this.om = new ObjectMapper();
    }

    public String[] getExportRequestCargoIds() {
        String method = "getExportRequestCargoIds";
        CallRequest req = new CallRequest();
        req.setPath(supplyContractPath);
        req.setMethod(method);
        req.setArgs(new String[] {});
        try {
            CallResponse callRes = sdk.call(req).send();
            if (callRes == null || callRes.getResult() == null || callRes.getResult().length == 0) {
                return new String[] {};
            }

            Integer[] ids = om.readValue(callRes.getResult()[0], Integer[].class);
            String[] idStrs = new String[ids.length];
            for (int i = 0; i < ids.length; i += 1) {
                idStrs[i] = String.valueOf(ids[i]);
            }
            return idStrs;
        } catch (JsonProcessingException jpe) {
            logger.error("parse json error:" + jpe);
            return new String[] {};
        }
    }

    public String[] getExportRequestByCargoId(String cargoId) {
        CallRequest req = new CallRequest();
        req.setPath(this.supplyContractPath);
        req.setMethod("getExportRequestByCargoId");
        req.setArgs(new String[] {cargoId});
        CallResponse callRes = sdk.call(req).send();
        return callRes.getResult();
    }

    public String getCargoURI(String cargoId) {
        CallRequest req = new CallRequest();
        req.setPath(this.supplyContractPath);
        req.setMethod("cargoURI");
        req.setArgs(new String[] {cargoId});
        CallResponse callRes = sdk.call(req).send();
        if (callRes.getResult() == null) {
            return null;
        }
        return callRes.getResult()[0];
    }

    public String transferPropose(
            String sender,
            String cargoId,
            String cargoURI,
            String fromChainPath,
            String toChainPath,
            String receiver,
            String hashLock,
            String lockTime) {
        Transaction tx = new Transaction();
        tx.setPath(this.portContractPath);
        tx.setMethod("transferPropose");
        tx.setSender(sender);
        tx.setNonce(Tool.randomNonce());
        tx.setArgs(
                new String[] {
                    cargoId, cargoURI, fromChainPath, toChainPath, receiver, hashLock, lockTime
                });
        Receipt receipt = sdk.sendTransaction(tx).send();
        if (receipt == null) {
            logger.info("no receipt recevied, try again");
            return null;
        }
        logger.info("receipt hash: " + receipt.getTransactionHash());
        return receipt.getTransactionHash();
    }

    public String[] getTransferingCargoIds(String portContractPath) {
        CallRequest req = new CallRequest();
        req.setPath(portContractPath);
        req.setMethod("getTransferingCargoIds");
        req.setArgs(new String[] {});
        CallResponse callRes = sdk.call(req).send();
        if (callRes == null || callRes.getResult() == null || callRes.getResult().length == 0) {
            logger.error("empty cargo ids...");
            return new String[] {};
        }
        try {
            if (callRes.getResult() == null) {
                logger.info("empty result, return empty ids");
                return new String[] {};
            }
            Integer[] ids = om.readValue(callRes.getResult()[0], Integer[].class);
            String[] idStrs = new String[ids.length];
            for (int i = 0; i < ids.length; i += 1) {
                idStrs[i] = String.valueOf(ids[i]);
            }
            return idStrs;
        } catch (JsonProcessingException jpe) {
            logger.error("parse json error:" + jpe);
            return new String[] {};
        }
    }

    @JsonFormat(shape = JsonFormat.Shape.ARRAY)
    @JsonPropertyOrder({
        "cargoURI",
        "fromChainPath",
        "toChainPath",
        "receiver",
        "cargoStatus",
        "hashLock",
        "secret",
        "lockTime"
    })
    public static class CargoInfo {
        public String cargoURI;
        public String fromChainPath;
        public String toChainPath;
        public String receiver;
        public String cargoStatus;
        public String hashLock;
        public String secret;
        public long lockTime;

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("CargoInfo: {\n");
            sb.append("  cargoURI      : " + cargoURI + "\n");
            sb.append("  fromChainPath : " + fromChainPath + "\n");
            sb.append("  toChainPath   : " + toChainPath + "\n");
            sb.append("  receiver      : " + receiver + "\n");
            sb.append("  cargoStatus   : " + cargoStatus + "\n");
            sb.append("  hashLock      : " + hashLock + "\n");
            sb.append("  secret        : " + secret + "\n");
            sb.append("  lockTime      : " + lockTime + "\n");
            sb.append("}\n");
            return sb.toString();
        }
    }

    public CargoInfo getCargoInfo(String portContractPath, String cargoId) {
        CallRequest req = new CallRequest();
        req.setPath(portContractPath);
        req.setMethod("getCargoInfo");
        req.setArgs(new String[] {cargoId});
        CallResponse callRes = sdk.call(req).send();
        if (callRes == null || callRes.getResult() == null || callRes.getResult().length == 0) {
            logger.error("empty cargo info...");
            return null;
        }

        try {
            CargoInfo ci = om.readValue(callRes.getResult()[0], CargoInfo.class);
            return ci;
        } catch (JsonProcessingException jpe) {
            logger.error("parse json error:" + jpe);
            return null;
        }
    }

    public String transferConfirm(
            String sender, String resourcePath, String cargoId, String secret) {
        Transaction tx = new Transaction();
        tx.setPath(resourcePath);
        tx.setMethod("transferConfirm");
        tx.setSender(sender);
        tx.setNonce(Tool.randomNonce());
        tx.setArgs(new String[] {cargoId, secret});
        Receipt receipt = sdk.sendTransaction(tx).send();
        if (receipt == null) {
            logger.info("no receipt recevied, try again");
            return null;
        }
        logger.info("receipt hash: " + receipt.getTransactionHash());
        return receipt.getTransactionHash();
    }
}
